PHP 8 — Key Features
Also Known As
TL;DR
Explanation
PHP 8.0 (2020): named arguments, union types, match expression (strict, no fall-through, returns a value), nullsafe operator (?->), JIT compiler, constructor property promotion, str_contains/str_starts_with/str_ends_with, throw as expression, attributes. PHP 8.1 (2021): enums, readonly properties, fibers, intersection types, never return type, array_is_list(), first-class callable syntax (strlen(...)), new in initializers. PHP 8.2 (2022): readonly classes, disjunctive normal form (DNF) types, deprecated dynamic properties, true/false/null standalone types. PHP 8.3 (2023): typed class constants, json_validate(), Override attribute, deep-cloning of readonly properties. PHP 8.4 (2024): property hooks, asymmetric visibility (public get / protected set), updated array functions (array_find, array_any, array_all), HTML5 parser for DOM extension.
Common Misconception
Why It Matters
Common Mistakes
- Using match without covering all cases — match throws UnhandledMatchError if no arm matches and there is no default; always include a default arm or ensure exhaustive coverage.
- Confusing readonly properties with immutable objects — readonly prevents reassignment after initialisation but does not deep-clone objects; a readonly property holding an object can still have its object's properties mutated.
- Using dynamic properties removed in PHP 8.2 — assigning to undeclared properties throws a deprecation in 8.1 and an error in 8.2; declare all properties explicitly.
- Expecting JIT to speed up database-heavy web requests — benchmark before enabling JIT; the overhead of JIT compilation can slow simple scripts.
Code Examples
// PHP 7 style — verbose, error-prone
function createUser(string $name, ?string $email = null, int $role = 0): User {
$this->name = $name;
$this->email = $email;
$this->role = $role;
}
$status = 'active';
$label = $status === 'active' ? 'Active' : ($status === 'pending' ? 'Pending' : 'Unknown');
// PHP 8 style — concise, type-safe
enum Status { case Active; case Pending; case Suspended; }
class User {
public function __construct(
public readonly string $name,
public readonly ?string $email = null,
public readonly Status $status = Status::Active,
) {}
}
$label = match($status) {
Status::Active => 'Active',
Status::Pending => 'Pending',
default => 'Unknown',
};