Table of contents
Open Table of contents
Overview
PHP 8.x represents a major leap in language expressiveness, bringing features like named arguments, enums, fibers, readonly properties, and property hooks. This post covers the key additions by version.
Version 8.0
Named Arguments
Pass arguments by name, in any order:
function createUser(string $name, string $email, string $role = 'user'): array {
return compact('name', 'email', 'role');
}
$user = createUser(email: 'jane@example.com', name: 'Jane');
Match Expression
A stricter alternative to switch — uses strict comparison, returns a value, and requires exhaustive matching:
$status = match ($code) {
200 => 'OK',
301 => 'Moved Permanently',
404 => 'Not Found',
500 => 'Internal Server Error',
default => 'Unknown',
};
Union Types
function processInput(string|int $input): string {
return match (true) {
is_string($input) => strtoupper($input),
is_int($input) => (string) ($input * 2),
};
}
Constructor Promotion
Combine parameter declaration and property assignment:
class Point {
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
Nullsafe Operator
$country = $user?->getAddress()?->getCountry()?->getCode();
Attributes
Replace docblock annotations with native attributes:
#[Route('/api/users', methods: ['GET'])]
public function listUsers(): Response {
// ...
}
Version 8.1
Enums
First-class enumerations:
enum Suit: string {
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
enum Color {
case Red;
case Green;
case Blue;
}
Enums support interfaces, methods, and constants:
enum Direction implements HasOpposite {
case North;
case South;
case East;
case West;
public function opposite(): self {
return match ($this) {
self::North => self::South,
self::South => self::North,
self::East => self::West,
self::West => self::East,
};
}
}
Fibers
Lightweight concurrency primitives for async operations:
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first');
echo "Resumed with: $value\n";
});
$result = $fiber->start(); // 'first'
$fiber->resume('second'); // prints: Resumed with: second
Readonly Properties
class User {
public function __construct(
public readonly int $id,
public readonly string $name,
) {}
}
Intersection Types
function process(Countable&Iterator $collection): void {
foreach ($collection as $item) {
// ...
}
}
First-Class Callable Syntax
$fn = strlen(...);
$result = array_map($fn, ['hello', 'world']); // [5, 5]
Version 8.2
Readonly Classes
All properties implicitly readonly:
readonly class Coordinate {
public function __construct(
public float $latitude,
public float $longitude,
) {}
}
Disjunctive Normal Form (DNF) Types
Combine union and intersection types:
function process((Countable&Iterator)|null $input): void {
// accepts Countable&Iterator or null
}
true, false, and null as Standalone Types
function alwaysTrue(): true {
return true;
}
Constants in Traits
trait HasVersion {
public const VERSION = '1.0';
}
Version 8.3
Typed Class Constants
class Config {
public const string APP_NAME = 'Code Kata';
public const int MAX_RETRIES = 3;
}
#[Override] Attribute
Explicitly mark method overrides — compile-time check that the parent method exists:
class ParentClass {
public function process(): void {}
}
class ChildClass extends ParentClass {
#[Override]
public function process(): void {
// guaranteed to override a parent method
}
}
Dynamic Class Constant Fetch
$constName = 'APP_NAME';
$value = Config::{$constName}; // 'Code Kata'
json_validate()
Validate JSON without decoding:
if (json_validate($input)) {
$data = json_decode($input);
}
Version 8.4
Property Hooks
Getters and setters directly in property declarations:
class Temperature {
public float $celsius {
get => ($this->fahrenheit - 32) * 5 / 9;
set => $this->fahrenheit = $value * 9 / 5 + 32;
}
public function __construct(
public float $fahrenheit,
) {}
}
Asymmetric Visibility
Different visibility for reading and writing:
class BankAccount {
public function __construct(
public private(set) float $balance,
) {}
public function deposit(float $amount): void {
$this->balance += $amount;
}
}
$account = new BankAccount(100.0);
echo $account->balance; // OK: public read
// $account->balance = 0; // Error: private set
new Without Parentheses in Expressions
$length = new String('hello')->length();
$items = new Collection([1, 2, 3])->filter(...);
array_find(), array_find_key(), array_any(), array_all()
$users = [['name' => 'Alice', 'active' => true], ['name' => 'Bob', 'active' => false]];
$found = array_find($users, fn($u) => $u['name'] === 'Alice');
$allActive = array_all($users, fn($u) => $u['active']); // false
$anyActive = array_any($users, fn($u) => $u['active']); // true
Key Takeaways
- Type system maturity — union types, intersection types, DNF types, typed constants, standalone types
- Data modeling — enums, readonly classes/properties, property hooks, asymmetric visibility
- Functional style — match expressions, first-class callables, named arguments
- Attributes — native metadata replacing docblock annotations
Related Katas
- Kata: Factory Pattern — enums + match expressions
- Kata: Strategy Pattern — interfaces and first-class callables
- Kata: Observer Pattern —
SplObserverinterface - Kata: Stack and Queue —
SplStackandSplQueue