Modern PHP Overview
Say "PHP" to a developer who last touched it in 2010 and you'll often get a wince — memories of untyped spaghetti scripts, mixed HTML and business logic, and a language that felt more like a templating hack than an engineering tool. That reputation was fair at the time, but it describes PHP 5, not the language as it exists today. PHP 8.0 through 8.3 (and beyond) is a genuinely different language: statically-analyzable, strictly typed where you want it to be, fast, and equipped with constructs that used to be reasons to reach for Java or C#. This page ties together the features covered elsewhere in this "Modern PHP" section and frames why each one matters.
The JIT compiler: PHP got fast
PHP 8.0 introduced a Just-In-Time compiler that sits alongside the existing opcode cache (OPcache). Traditionally, PHP compiled your source into bytecode and interpreted that bytecode on every request — fast to start, but with a ceiling on raw computational throughput. The JIT can compile hot bytecode paths down to native machine code at runtime, which mostly helps CPU-bound work (image processing, numeric algorithms, compression) rather than the typical "read database, render template" web request, since that kind of request is usually bottlenecked on I/O, not CPU. Most applications won't feel a dramatic difference from the JIT alone, but its presence signals a shift: PHP's engine team started optimizing for workloads well beyond simple page rendering.
Types: from optional hints to a real type system
PHP 7 introduced scalar type hints and return types, but PHP 8 made the type system dramatically more expressive. Union types (int|string) let a parameter or return value declare more than one acceptable type. Intersection types (Countable&Iterator), added in 8.1, require a value to satisfy multiple interfaces at once. The mixed type makes "accepts literally anything" an explicit, intentional declaration instead of an implicit gap left by omitting a type. And the never return type marks functions that never return normally at all — they always throw or halt execution.
A taste of PHP 8's type system
<?php
function normalize(int|string $value): string
{
return (string) $value;
}
function fail(string $message): never
{
throw new RuntimeException($message);
}
function process(Countable&Iterator $collection): int
{
return count($collection);
}These aren't cosmetic — a richer type system means static analysis tools (PHPStan, Psalm) and your IDE can catch entire categories of bugs before the code ever runs, closing much of the gap that used to separate PHP from statically-typed languages.
Enums: a real closed set of values
PHP 8.1 added enums as a first-class language construct, replacing the old workaround of a class full of public const declarations. An enum can be "pure" (just a set of named cases) or "backed" by a string or int for storage and serialization, and — like a class — it can implement interfaces and declare methods. Enums are covered in full depth elsewhere in this section; the short version is that they turn "this string should only ever be one of four values" from a convention enforced by code review into a rule enforced by the engine itself.
Readonly properties: immutability without boilerplate
PHP 8.1's readonly modifier lets a class property be written exactly once — typically inside the constructor — and never reassigned afterward. Before this existed, developers who wanted immutable objects had to enforce it themselves, either by convention or by writing a private property with only a getter and no setter. readonly makes the intent explicit at the property declaration and lets PHP itself throw an error on a second write attempt.
A value object with readonly properties
<?php
final class Money
{
public function __construct(
public readonly int $amountInCents,
public readonly string $currency,
) {
}
}
$price = new Money(1999, 'USD');
// $price->amountInCents = 500; // Error: Cannot modify readonly propertyFirst-class callable syntax
PHP 8.1 also introduced strlen(...) syntax for turning any named function, static method, or instance method directly into a Closure, replacing the older and error-prone patterns of passing a function name as a string ('strlen') or a [$object, 'method'] array. Because the first-class callable syntax references a real, existing symbol at the point it's written, your IDE and static analysis tools can validate it and follow "go to definition," which was never reliably possible with string-based callables. This is covered in depth on its own page.
Named arguments: self-documenting call sites
PHP 8.0's named arguments let you pass arguments by parameter name instead of strictly by position, and in any order you like. This is especially useful for functions with several optional parameters, where previously you'd have to pass every parameter up to the one you actually cared about, in order, or rely on positional placeholders.
Named arguments skip positional placeholders
<?php
function createUser(
string $name,
string $email,
bool $isAdmin = false,
bool $isActive = true,
): array {
return compact('name', 'email', 'isAdmin', 'isActive');
}
// Only override $isAdmin, skip everything positional in between
$user = createUser(name: 'Amara', email: 'amara@example.com', isAdmin: true);Fibers: cooperative multitasking as a primitive
PHP 8.1 introduced Fibers, a low-level primitive that lets a chunk of code pause its own execution mid-function and resume later, without unwinding the call stack. Most application developers will never call Fiber::start() directly — instead, Fibers act as the foundation that async libraries such as Amp and ReactPHP-adjacent tooling build on to offer concurrency without callback-based code. Fibers get a dedicated page in this section since the mental model takes a little getting used to.
Attributes: structured metadata instead of docblocks
PHP 8.0's attributes (#[Attribute] syntax) replace the older convention of embedding machine-readable metadata inside /** */ docblock comments — the kind of thing frameworks like Symfony and Doctrine used to parse out of comments like @Route("/users"). Attributes are real PHP syntax, parsed by the engine and readable through the Reflection API, rather than text that happens to live inside a comment. This section has a dedicated page walking through reading and writing your own attributes.
Nullsafe operator and match: less ceremony
Two smaller but frequently-used PHP 8.0 additions round out the
picture. The nullsafe operator ?-> short-circuits an entire
chain of property and method accesses the moment any link
evaluates to null, replacing verbose nested isset() checks.
The match expression, also new in 8.0, is a stricter, expression-based
alternative to switch — it uses strict comparison, has no
fallthrough between cases, and returns a value directly.
Nullsafe chaining and match together
<?php
$city = $order?->customer?->address?->city ?? 'Unknown';
$label = match (true) {
$order === null => 'No order',
$order->isPaid() => 'Paid',
default => 'Pending',
};Feature summary
Feature | Introduced in |
|---|---|
Named arguments | PHP 8.0 |
Union types | PHP 8.0 |
Attributes | PHP 8.0 |
Nullsafe operator ( | PHP 8.0 |
| PHP 8.0 |
JIT compiler | PHP 8.0 |
Enums | PHP 8.1 |
Readonly properties | PHP 8.1 |
First-class callable syntax | PHP 8.1 |
Intersection types | PHP 8.1 |
| PHP 8.1 |
Fibers | PHP 8.1 |
Readonly classes | PHP 8.2 |
Typed class constants | PHP 8.3 |
PHP 8.0 was the big turning point: JIT, union types, attributes, nullsafe operator,
match, and named arguments all landed in one release.PHP 8.1 focused on data modeling and concurrency primitives: enums, readonly properties, intersection types, first-class callables, and Fibers.
PHP 8.2 and 8.3 have continued refining the type system (readonly classes, typed class constants) rather than introducing another single headline feature.
Static analysis tools (PHPStan, Psalm) and IDEs benefit directly from every one of these features — richer syntax means more bugs caught before runtime.