Skip to content
Code Kata
Go back

PHP 8.x: Key Updates by Version

Suggest edit

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

  1. Type system maturity — union types, intersection types, DNF types, typed constants, standalone types
  2. Data modeling — enums, readonly classes/properties, property hooks, asymmetric visibility
  3. Functional style — match expressions, first-class callables, named arguments
  4. Attributes — native metadata replacing docblock annotations

Suggest edit
Share this post on:

Previous Post
PHP Core Concepts
Next Post
TypeScript Core Concepts