Imagine it's release day. You update your Dockerfile, bump composer.json, and the changelog for PHP 9.0 scrolls past: extension methods, structs, modules, surfaces, and generics. This section is a guided tour of that release — what's new, why it's shaped the way it is, and what it feels like to write PHP with it.
Now the honest part: PHP 9.0 doesn't exist, and none of this is accepted. Everything described here comes from a set of draft RFCs and proposals — most of them mine — that are somewhere between "implemented on a branch and under discussion" and "working draft." This tour assumes every one of them makes it through the RFC process and ships as currently described. Some will change in discussion. Some may be voted down. Some interact with each other in ways the drafts haven't pinned down yet; as those corners get defined, these pages will grow to cover them.
In the spirit of this site: all of it is eventually wrong — some of it possibly before it ships. Treat this as a preview of a direction, not documentation of a release.
The chapters build on one another, so first-timers should read them in order. Each one stays at the "what this means for your code" level; the RFCs themselves carry the edge cases, engine mechanics, and open issues, and each chapter links to its sources.
If you'd rather read code than prose, NineLine is a small standard library that uses all four features together — generic value-struct collections, typed delegates, scalar extension methods, the whole thing behind one module. These chapters borrow its vocabulary (Sequence<T>, Map<V>, Option<T>, Range) so the two can be read side by side.
PHP 9.0 lets you declare new methods for classes you don't own — and for strings and arrays — without wrappers, subclasses, or Closure::bind gymnastics. One feature, three sizes: a block in a file for your app, a named extension your packages import through Composer, and the same two forms again on scalars.
Read →
Arrays copy but have no shape; objects have shape but always share. PHP 9.0's structs are the missing quadrant: typed, named, fixed-shape values that copy on assignment like arrays do. They have methods, interfaces, and traits — everything a class has, minus exactly the parts that would contradict being a value. And with the mutating marker, a method can update the value it's called on without ever becoming an aliasing loophole. One rule about $this explains the whole design.
Read →
Namespaces organize code but encapsulate nothing — every public class is reachable by anyone who spells its name. PHP 9.0 modules turn the @internal docblock into an engine-enforced boundary: members declare `module`, the module exports its public surface, and consumers come in through `use module` and `:>` — or hoist a single member to the file root with `use Module:>Class`.
Read →
public, protected, private — and now internal — answer 'who may call this?' with 'everyone, my subclasses, me, or my module.' PHP 9.0's surfaces add the answer none of them can give: 'code acting in a particular role.' Test hooks, framework wiring, and lifecycle plumbing get named audiences — and callers declare their role in one greppable line.
Read →
Parameterized types are already the most-used feature of PHP's type system — they just live in docblocks the engine can't read. PHP 9.0's generics make the type argument real: Sequence<int> is a genuine class, enforced by the same runtime machinery as any hand-written one, and it shares its template's bytecode so the hot path costs nothing. The trick is monomorphization, and every reason it was 'impossible' expired years ago. Now covering v0.10 — parameter-dependent extends, type-parameter packs — and the companion draft that lets a call site write the type argument: map<Price>($fn).
Read →