Every few years, a PHP RFC comes along that’s interesting enough to make me pause—and ask a simple question:

Is this actually solving a problem we can’t already solve?

The new Context Managers RFC introduces a using keyword intended to mimic Python’s with statement. It aims to make resource handling cleaner by automatically running setup and teardown code around a scoped block.

Here’s the motivating example from the RFC. Today, you write something like:

$fp = fopen('file.txt', 'w');
if ($fp) {
    try {
        foreach ($someThing as $value) {
            fwrite($fp, serialize($value));
        }
    } catch (\Exception $e) {
        log('The file failed.');
    } finally {
        fclose($fp);
    }
}
unset($fp);

And the RFC wants to turn it into:

using (file_for_write('file.txt') as $fp) {
    foreach ($someThing as $value) {
        fwrite($fp, serialize($value));
    }
}
// At this point, $fp is guaranteed to be closed.

Is this nicer? Maybe. But after seeing how cleanly a simple helper function solves this, I don’t think it’s nicer at all—just added syntax.

But it also adds a new keyword, new syntax rules, new magic, and new surface area for a language that already isn’t small.

And that’s where my problem starts.


Fewer Keywords, Not More

I generally prefer languages to remove keywords, not add them.

Complexity doesn’t come from what a language can do. It comes from the surface area developers must memorize and explain. PHP has 20+ years of accumulated edges and inconsistencies—implode(), I’m looking at you. Adding another keyword because “Python has it” isn’t a strong argument.

Ergonomics only matter if:

  1. the new construct is actually clearer, and
  2. the problem wasn’t already trivially solvable.

And this particular problem is extremely solvable today.


This Is a Design Problem—Not a Language Problem

If your codebase has:

  • raw fopen() / fclose() sprinkled everywhere
  • cleanup logic duplicated
  • exception paths handled inconsistently
  • resource handling mixed in with business logic

…a new language feature won’t fix any of that.

You don’t need a keyword.
You need better patterns:

  • centralize resource handling
  • wrap dangerous operations
  • expose higher-level abstractions
  • use try/finally in one place, not twenty

And PHP already makes this easy.


A Simple Helper Already Solves the Entire Problem

Everything the RFC wants can be expressed in plain PHP:

function with_file_for_write(string $path, callable $callback): void {
    $fp = fopen($path, 'w');
    if (!$fp) {
        throw new RuntimeException("Unable to open file: $path");
    }

    try {
        $callback($fp);
    } finally {
        fclose($fp);
    }
}

Usage:

with_file_for_write('file.txt', function($fp) {
    fwrite($fp, "Hello, world!");
});

This is:

  • explicit
  • safe
  • readable
  • easy to test
  • and requires zero new syntax

Hard to beat.


The RFC’s Own Example Is Half-Baked

Here’s the part that really exposes the issue:

The RFC’s example assumes that a function named file_for_write() already exists—but never shows how it works.

And here’s the twist:

The actual file_for_write() implementation is more boilerplate than the helper function above—not less.

To be compatible with using, it must:

  • implement a brand-new ContextManager interface
  • define enter()
  • define leave()
  • track internal state
  • handle exceptions in open, write, and cleanup
  • return something meaningful for the as $fp binding

A realistic implementation looks like this:

class FileWriterContext implements \ContextManager {
    private $fp;
    private $path;

    public function __construct(string $path) {
        $this->path = $path;
    }

    public function enter() {
        $this->fp = fopen($this->path, 'w');
        if (!$this->fp) {
            throw new RuntimeException("Unable to open file: {$this->path}");
        }
        return $this->fp;
    }

    public function leave(?Throwable $exception): void {
        if ($this->fp) {
            fclose($this->fp);
        }
    }
}

function file_for_write(string $path): FileWriterContext {
    return new FileWriterContext($path);
}

Compare that to the straightforward helper function above. It’s not even close in terms of clarity or simplicity.

The RFC’s promise of “cleaner code” evaporates once you see the actual boilerplate hidden behind the example. It doesn’t reduce complexity—it merely hides it behind a new keyword, new semantics, and a new control structure.


But Wait, We Can Do Even Better

If you really want context managers, you don’t need a new keyword for that either. You can build the entire pattern yourself in plain PHP:

interface ContextManager {
    public function enter();
    public function leave(?Throwable $exception): void;
}

function using(ContextManager $context, callable $callback): void {
    $value = $context->enter();

    try {
        $callback($value);
    } catch (Throwable $e) {
        $context->leave($e);
        throw $e;
    }

    $context->leave(null);
}

Usage:

using(new FileWriterContext('file.txt'), function ($fp) {
    fwrite($fp, "Hello, world!");
});

This already gives you the same behavior the RFC is proposing—without new syntax, new parser rules, new engine behavior, or a new keyword.

But we can go even further and make the API even nicer by baking the pattern directly into the context class:

class FileWriterContext implements ContextManager {
    // same enter/leave as above...

    public static function with(string $path, callable $callback): void {
        $ctx = new self($path);
        $fp = $ctx->enter();

        try {
            $callback($fp);
        } finally {
            $ctx->leave(null);
        }
    }
}

// Usage
FileWriterContext::with('file.txt', function ($fp) {
    fwrite($fp, "Hello again!");
});

This is clean, explicit, testable, and easy to extend.
You could package this as a small library tomorrow, and it would work in every version of PHP going back years.

At the end of the day, these examples all demonstrate the same thing: a context manager is just a wrapper that runs setup, then your callback, then teardown. You can call it a decorator, a template method, or simply “a function that does something before and after my code.”

None of this requires modifying the language.


A Light Touch on “Resource Acquisition Is Initialization”

Some people frame this RFC as PHP inching toward something like Resource Acquisition Is Initialization (RAII)—a model from languages like C++ and Rust where acquiring a resource and releasing it are tied to object scope. When the object goes out of scope, a destructor always runs.

That’s a neat idea in languages with deterministic destructors. PHP is garbage-collected and request-scoped, so RAII-style guarantees don’t really map the same way.

Most PHP resource safety comes from patterns, not compiler-enforced destructor semantics. And those patterns—centralized helpers, try/finally discipline, and keeping raw streams out of business logic—already work perfectly well.

We don’t need a keyword to enforce something PHP is already good at when used properly.


Conclusion: More Code for Less Value

I’m not against improving the language. Typed properties, union types, match expressions, attributes, and enums have all pushed PHP in a cleaner and more modern direction. Those additions solve real problems without adding unnecessary surface area. But I’m very much against pretending that a new keyword automatically creates clarity.

The RFC’s own example hides the fact that its implementation requires:

  • more code
  • more magic
  • more moving parts
  • more mental overhead
  • and more language surface area

Meanwhile, the helper pattern gives you the same guarantees without adding anything new to the language.

No new rules.
No new constructs.
No new boilerplate.
No new magic.

Just plain PHP.

Sometimes the best thing we can do for the language is… nothing.