Re: [RFC] Context Managers, v2
| From: | Larry Garfield | Date: | Fri, 06 Nov 2026 14:49:37 +0000 |
| Subject: | Re: [RFC] Context Managers, v2 | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-133029@lists.php.net to get a copy of this message | ||
On Fri, Nov 6, 2026, at 3:40 AM, David Maye wrote:
> Hello,
Please remember to bottom post, not top post.
> I agree that
as seems more coherent than
> => in PHP. The only time I
> saw => in PHP was with key => value. And in JS it was
> with () => {}
> functions.
>
> I have another question. This mechanism of Context Manager, Is it the
> same as the Ruby do blocks? Example with Vagrant Ruby
> configuration
> file:
>
> ```
> Vagrant.configure("2") do |config|
> config.vm.box = "bento/ubuntu-24.04"
> config.vm.box_version = "202510.26.0"
> end
It's not quite the same. I don't have any Ruby experience, so going based on https://zetcode.com/ruby/do-keyword/, it can be
used in a similar way but it's not the same mechanism. Ruby (per that page) creates a closure
for a do block, and thus variables go out of scope at the end and get closed implicitly. A using
block in this RFC creates no closure or scope. The body of the using expression executes in the
same function context as the code around it. Also, the context manager generator itself allows more
cleanup than just "unset()" on the context variable. That happens automatically, which
can trigger some cleanup, but the context manager is able to do a lot more if it wants to.
The model we are building from is Python, basically.
> When you set the example as:
>
> ```
> using (file_for_write('file.txt') => $fp) {
> foreach ($someThing as $value) {
> fwrite($fp, serialize($value));
> }
> };
> // At this point, we're guaranteed that $fp has closed, whether there
> was an error or not.
> ```
>
> It means that the block inside using is aware of variables
> outside of
> it's own scope, right?
Correct. There is no "own scope". As implemented currently, it's just materializing
some generator read logic around the body of the using block. There is no new function scope
created, so the code inside is "in" the function it's already in, as it always has
been.
> I like the idea. I have to admit that at first I was confused because I
> thought that the first example in the RFC post was exactly the same as
> the second example (Error catching included).
You mean in the introduction? They do accomplish the same goal, just with less code in the context
manager version.
> I have another question: according to PHP documentation ("Freeing
> resources"
>
>
> https://www.php.net/manual/en/language.types.resource.php#:~:text=makes%20no%20sense.-,Freeing%20resources,-%C2%B6):
>
> "Thanks to the reference-counting system being part of the Zend Engine,
> a resource with no more references to it is detected automatically, and
> it is freed by the garbage collector. For this reason, it is rarely
> necessary to free the memory manually."
>
> From what I understand by this text, fclose() is optional (but
> recommended according to some Stack Overflow posts). In both cases
> (using or not the using using), this acts exactly the same. The
> only
> difference is that you declare the variable inside the using instead of
> the outside the try/catch block, right? Because even inside the
> using
> function you would need to invoke the fclose() function, right?
PHP will automatically close() a resource that goes out of scope, just as it will garbage collect an
object that goes out of scope. However, that is only true if that is the last reference to that
resource/object. If the value had "escaped" (see the section in the RFC), then the file
wouldn't be closed at that point. Also, even if PHP will cleanup as it goes, it's still
good practice to close a file when you're done with it to minimize race conditions. A using
block takes care of both of those for you. The context manager generator function is where the
fclose() would live, so you don't have to remember to write it yourself every time.
> Apart from easing the expressiveness of the language, I'm curious of
> what would be other benefits. In the post you express that it provides
> "a way to abstract out common control-flow and
> variable-lifetime-management pattern". What are the ways this function
> may bring such improvements? Are those improvement observed by language
> like Python not already available in PHP by default? I read somewhere
> that PHP tends to be more memory efficient than Python due to the
> processing of the many magic methods and other issues. If so, Is the
> context manager in Python focused on improving the
> variable-lifetime-management due to Python internal issues, and are
> these issues present in PHP?
The examples section includes most of the example cases I could think of off hand, but I'm sure
there are more. Basically, any time you have the use case of "I want to stick some setup logic
at the start of this code, and some cleanup and error handling logic at the end", a context
manager can abstract that away for you, resulting in reusable logic and in your business logic not
getting confused with the boilerplate error handling.
Think of opening a connection to some remote service and sending a message to it, but then
there's like 10 error cases you need to handle. That's a great example to factor out into
a using block, so you don't need to do all that error handling each time.
For use in Python, I recommend this video, which lays out a "Pythonic" way of thinking,
and in particular shows how context managers in Python can help vastly simplify code.
https://www.youtube.com/watch?v=wf-BqAjZb8M
(That's the video that really helped me wrap my head around context managers and made me want
them in PHP.)
As far as performance, there's a huge number of differences between PHP and Python that impact
performance. The engine itself is less efficient, plus yes, Python relies a lot more heavily on
magic callbacks that incur extra overhead (just as they do in PHP). You can't really pin it
down to any one thing. Context Managers in Python in particular are mainly about managing flow of
control and error handling, not performance, which is the same for this RFC.
The way using is implemented here, that impact is minimal. It adds a generator invocation and read,
and that's about it. So it's not 100% free, but the overhead is minimal, and comparable
to if you were passing a closure to some ordinary function that did the same sort of wrapping.
--Larry Garfield