[PHP-DEV] [RFC] Context Managers, v2

Hi folks. Based on the feedback and problems with the previous Context Managers RFC, Arnaud and I have a new approach to present:

It basically swaps the two-method approach in favor of "it's a generator, you're welcome." The result is, I think, both simpler and more flexible, and resolves a number of other issues as well. More details in the RFC.

There is a linked implementation, though Arnaud warns it's only partially complete so go easy on it. :slight_smile: But the design in the RFC is all ready for review.

--
  Larry Garfield
  larry@garfieldtech.com

On Thu, Nov 5, 2026, at 12:18 PM, Stephen Reay wrote:

Hi Larry,

TLDR: why `=>` rather than `as`?

As a userland developer I like what this is trying to achieve. I'm not
knowledgeable enough about the internals to comment on the finer
details or their ramifications - I just have a bike shedding
suggestion/question which seemed glaring to me so I wonder if it's
because of an ambiguous syntax or some other issue?

The `=>` symbol seems odd to me, given how it's used elsewhere
(generally as an indicator of a key/value pair, whether in an array or
match block) and how generators/iterables are used elsewhere (ie
foreach as)

Your example is:

using (file_for_write('file.txt') => $fp) {

Whereas to my eye this seems like a much more obvious choice:

using (file_for_write('file.txt') as $fp) {

Anyway, just a thought.

The v1 RFC originally used `as`, which is what Python uses as well. However, a lot of people were concerned that it implied that the left side expression would be "used as" the right side variable. While in some cases it might be, the typical case is that it's not. The context variable is emitted by the context manager, but is not the same thing.

At the time, no one could really agree on a better keyword. But => was right there waiting to be used, and already has an "evaluates to" or "yields" or "produces" meaning, which is roughly accurate to what happens here.

I don't mind `as`, personally, but the consensus before seemed to not care for it.

--Larry Garfield

Hello Larry,

Using the => operator for this case looks inconsistent to me as well. In my perception, the meaning of => in PHP is that of mapping as a function in the mathematical sense: the domain mapped to the image. For example, we have keys mapped to values in arrays or in yield generators or even in iterations with foreach. Similarly, the usage of => in lambda functions is also consistent as we have a mapping of input to output.

In this new case, however, it looks like it is used more as an assignment. In the statement foreach ($iterable as $key => $value), the assignment operator is as, while => consistently carries the meaning of a map. So, I would argue that using as for assigning is more consistent with the previous syntax choices.

···

On Thu, 5 Nov 2026 at 19:33, Larry Garfield <larry@garfieldtech.com> wrote:

Lazare INEPOLOGLOU
Architecte Logiciel

Hello,

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

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?

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).

I have another question: according to PHP documentation (“Freeing resources”
PHP: Resources - Manual):

“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?

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?

Nonetheless, I enjoyed reading this post. Reminds me of Ruby. I like the idea​:light_bulb:. I just got curious :smiley:

On Nov 6, 2026 9:00 AM, Lazare Inepologlou linepogl@gmail.com wrote:

On Thu, 5 Nov 2026 at 19:33, Larry Garfield <larry@garfieldtech.com> wrote:

On Thu, Nov 5, 2026, at 12:18 PM, Stephen Reay wrote:

Hi Larry,

TLDR: why => rather than as?

As a userland developer I like what this is trying to achieve. I’m not
knowledgeable enough about the internals to comment on the finer
details or their ramifications - I just have a bike shedding
suggestion/question which seemed glaring to me so I wonder if it’s
because of an ambiguous syntax or some other issue?

The => symbol seems odd to me, given how it’s used elsewhere
(generally as an indicator of a key/value pair, whether in an array or
match block) and how generators/iterables are used elsewhere (ie
foreach as)

Your example is:

using (file_for_write(‘file.txt’) => $fp) {

Whereas to my eye this seems like a much more obvious choice:

using (file_for_write(‘file.txt’) as $fp) {

Anyway, just a thought.

The v1 RFC originally used as, which is what Python uses as well. However, a lot of people were concerned that it implied that the left side expression would be “used as” the right side variable. While in some cases it might be, the typical case is that it’s not. The context variable is emitted by the context manager, but is not the same thing.

At the time, no one could really agree on a better keyword. But => was right there waiting to be used, and already has an “evaluates to” or “yields” or “produces” meaning, which is roughly accurate to what happens here.

I don’t mind as, personally, but the consensus before seemed to not care for it.

–Larry Garfield

Hello Larry,

Using the => operator for this case looks inconsistent to me as well. In my perception, the meaning of => in PHP is that of mapping as a function in the mathematical sense: the domain mapped to the image. For example, we have keys mapped to values in arrays or in yield generators or even in iterations with foreach. Similarly, the usage of => in lambda functions is also consistent as we have a mapping of input to output.

In this new case, however, it looks like it is used more as an assignment. In the statement foreach ($iterable as $key => $value), the assignment operator is as, while => consistently carries the meaning of a map. So, I would argue that using as for assigning is more consistent with the previous syntax choices.

Lazare INEPOLOGLOU
Architecte Logiciel

Am 05.11.2026, 18:33:14 schrieb Larry Garfield <larry@garfieldtech.com>:

Hi folks. Based on the feedback and problems with the previous Context Managers RFC, Arnaud and I have a new approach to present:

https://wiki.php.net/rfc/context-managers-v2

trying to understand, can you implement the file_for_write method in the code sample to its more clear how that creates a generator?

It basically swaps the two-method approach in favor of “it’s a generator, you’re welcome.” The result is, I think, both simpler and more flexible, and resolves a number of other issues as well. More details in the RFC.

There is a linked implementation, though Arnaud warns it’s only partially complete so go easy on it. :slight_smile: But the design in the RFC is all ready for review.

–
Larry Garfield
larry@garfieldtech.com

Le 05/11/2026 à 18:33, Larry Garfield a écrit :

It basically swaps the two-method approach in favor of "it's a generator, you're welcome." The result is, I think, both simpler and more flexible, and resolves a number of other issues as well. More details in the RFC.

Hello,

I love the simplicity for this RFC. I have one question: what happens if the generator yield more than once, will the using block run more than once ? If so it seems odd and may trigger very hard to debug problems (I can imagine some SQL transaction running more than once because of a generator bug).

Regards,

--

Pierre

Le 06/11/2026 à 12:19, Pierre a écrit :

I love the simplicity for this RFC. I have one question: what happens if the generator yield more than once, will the using block run more than once ? If so it seems odd and may trigger very hard to debug problems (I can imagine some SQL transaction running more than once because of a generator bug).

The implementation details section gave me an answer, the generator can only yield once.

Sorry for the noise.

--

Pierre

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 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\(&#39;file\.txt&#39;\) =&gt; $fp\) \{
&nbsp;&nbsp;&nbsp;&nbsp;foreach \($someThing as $value\) \{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fwrite\($fp, serialize\($value\)\);
&nbsp;&nbsp;&nbsp;&nbsp;\}
\};
// At this point, we&#39;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&#39;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"

PHP: Resources - Manual):

"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.

(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

On Thu, Nov 5, 2026, at 18:33, Larry Garfield wrote:

Context Managers RFC, Arnaud and I have a new approach to present:

https://wiki.php.net/rfc/context-managers-v2

Thanks. It looks promising.

When the using expression ends, VAR is explicitly unset().

What happens when VAR was already defined before the context? Is it masked and restored, or overwritten and unset?

what the return value of a combined using block should be

I would say the return value of the outer-most / left-most context manager. That is equivalent to the nested form.
The compact comma separated syntax could also be added later, so if this is a difficult point it is also possible to remove this from the RFC and add it later on.

public function transaction(): self

Should the return type be Generator here?

Regards,

Sjoerd

On Fri, Nov 6, 2026, at 5:12 AM, Benjamin Außenhofer wrote:

Am 05.11.2026, 18:33:14 schrieb Larry Garfield <larry@garfieldtech.com>:

Hi folks. Based on the feedback and problems with the previous Context Managers RFC, Arnaud and I have a new approach to present:

PHP: rfc:context-managers-v2

trying to understand, can you implement the file_for_write method in
the code sample to its more clear how that creates a generator?

I added a basic implementation to the intro section. Arguably I'd clean it up further and refine it for a real function, but I kept it parallel to the example to make it clearer what is going on.

--Larry Garfield

On Fri, Nov 6, 2026, at 8:51 AM, Sjoerd Langkemper wrote:

On Thu, Nov 5, 2026, at 18:33, Larry Garfield wrote:

Context Managers RFC, Arnaud and I have a new approach to present:

PHP: rfc:context-managers-v2

Thanks. It looks promising.

When the using expression ends, VAR is explicitly unset().

What happens when VAR was already defined before the context? Is it
masked and restored, or overwritten and unset?

At the moment, in v2 it's overwritten and unset. We can bring back the mask/restore logic if there's interest, but we're not entirely convinced it's needed so left it out of this version for now. (Basically, if you're reusing a variable like that, you know that it's not going to be what it was before. If you do so accidentally, then your function is too long for you to keep in your head and you need to refactor anyway.)

what the return value of a combined using block should be

I would say the return value of the outer-most / left-most context
manager. That is equivalent to the nested form.

That's my thinking as well, but we wanted to get input from the Crowd(tm).

The compact comma separated syntax could also be added later, so if
this is a difficult point it is also possible to remove this from the
RFC and add it later on.

True, though it doesn't seem controversial so I'd rather include it if we can.

public function transaction(): self

Should the return type be Generator here?

Yep, it should. Fixed now, thanks.

--Larry Garfield