PHP 8.4 added asymmetric visibility, which lets a property be public to read but private(set) or protected(set) to write. This RFC from Holly Schilling makes writes to those properties as fast as writes to a plain public property, without changing how any code behaves.

Why writes are slow today

PHP checks normal visibility, like public or private, once per line of code. After the first run, that line stores the result in a runtime cache and skips the check from then on.

Asymmetric visibility doesn't get that cache, so PHP checks write access again on every single write. The reason is that the code that looks up a property doesn't know whether you're reading or writing it.

The RFC measured the cost. A write to a private(set) property takes about 3 times as long as a write to a public typed property, and protected(set) takes about 4 times as long.

What changes

The property lookup code learns whether it's serving a read or a write. For writes, PHP checks set access once, when it fills the cache for that line, and after that the write is as fast as a public one.

In the RFC's benchmark, a private(set) write went from 3.04 to 1.04 nanoseconds, while a public write took about 1.02. Reads didn't change. With opcache, which is on by default as of PHP 8.5, the gap closes too.

The savings apply per line of code, not per object, so they show up where the same assignment runs many times:

  • An ORM hydrating thousands of rows
  • Counters or parsers that update a property in a loop
  • Builders and fluent APIs

Some paths keep their current checks, including readonly, static properties, += and ++, and the JIT compiler. Under the JIT, asymmetric writes stay about 2.5 times slower than public ones, and the RFC lists that as future work.

What it means for existing code

Nothing changes in how your code behaves. Error messages, __set fallbacks, reflection and readonly all work the same, and the RFC says the full test suite passes without changes. The only difference you could see is timing: a line that's denied write access gets checked every time, but it always ends in an error or a __set call anyway.

Where it stands

It's a draft, dated July 14, 2026, and the code is done in a pull request of about 80 lines. The planned vote asks to add it to PHP 8.6 if the release managers accept it, or PHP 8.7 otherwise. It would need a two-thirds majority.