PHP 8+ Features

A tour of every feature that makes modern PHP enjoyable: match, nullsafe, union types, constructor promotion, enums, readonly, fibers, first-class callable, and more.

Intermediate 11 min read 14 examples

Why Upgrade to PHP 8?

PHP 8 (Nov 2020) and its 8.1, 8.2, 8.3 successors added the language features PHP developers had been asking for for a decade. Plus the JIT, performance gains, and better type safety throughout.

Named Arguments (PHP 8.0)

PHP
<?php
// Skip middle defaults
htmlspecialchars($input, double_encode: false);

// Self-documenting calls
$user = createUser(
    name: "Ruban",
    email: "x@y.com",
    role: "admin",
    active: true,
);

match Expression (PHP 8.0)

PHP
<?php
$label = match ($status) {
    1, 2 => "Active",
    3    => "Pending",
    4    => "Banned",
    default => "Unknown",
};

Returns a value, uses strict comparison (===), throws on no match unless default. Replaces 90% of switch statements.

Nullsafe Operator ?-> (PHP 8.0)

PHP
<?php
// Old way
$country = null;
if ($user !== null && $user->address !== null) {
    $country = $user->address->country;
}

// PHP 8.0+
$country = $user?->address?->country;

Union & Intersection Types

PHP
<?php
// Union (8.0) - accept multiple types
function format(int|string $value): string {
    return (string) $value;
}

// Intersection (8.1) - must satisfy MULTIPLE interfaces
function process(Iterator&Countable $data): void { /* ... */ }

// Disjunctive Normal Form (8.2) - union of intersections
function handle((Iterator&Countable) | array $input): void { /* ... */ }

// New built-in types: false, true, null as standalone (8.2)
function example(): false|string { return "ok"; }

Constructor Property Promotion (PHP 8.0)

PHP
<?php
// Before
class User {
    private string $name;
    public function __construct(string $name) {
        $this->name = $name;
    }
}

// After
class User {
    public function __construct(private string $name) {}
}

Attributes (PHP 8.0)

First-class metadata replacement for docblock annotations:

PHP
<?php
#[Attribute]
class Route {
    public function __construct(public string $path) {}
}

class UserController {
    #[Route("/users")]
    public function index() { /* ... */ }

    #[Route("/users/{id}")]
    public function show(int $id) { /* ... */ }
}

// Reflect at runtime
$ref = new ReflectionMethod(UserController::class, "show");
foreach ($ref->getAttributes(Route::class) as $attr) {
    $route = $attr->newInstance();
    echo $route->path;        // "/users/{id}"
}

Enums (PHP 8.1)

PHP
<?php
enum Status: string {
    case Pending = "pending";
    case Active  = "active";
    case Banned  = "banned";

    public function color(): string {
        return match($this) {
            self::Pending => "yellow",
            self::Active  => "green",
            self::Banned  => "red",
        };
    }
}

$s = Status::Active;
echo $s->value;            // "active"
echo $s->color();          // "green"

// From a stored value
$s = Status::from("active");        // Status::Active
$s = Status::tryFrom("xxx");        // null - no exception

readonly Properties & Classes

PHP
<?php
// Property-level readonly (8.1)
class Money {
    public function __construct(
        public readonly int $cents,
        public readonly string $currency,
    ) {}
}

$m = new Money(1999, "USD");
// $m->cents = 0;   // ERROR - readonly

// Class-level readonly (8.2) - all properties readonly
readonly class Point {
    public function __construct(
        public float $x,
        public float $y,
    ) {}
}

First-Class Callable Syntax (PHP 8.1)

PHP
<?php
// Before
$upper = array_map("strtoupper", $words);

// PHP 8.1 - IDE-aware, refactor-safe
$upper = array_map(strtoupper(...), $words);

// Works with methods too
$saver = $repo->save(...);
$saver($entity);

Fibers (PHP 8.1)

Primitives for cooperative concurrency. You probably won't write fibers directly but they power async frameworks:

PHP
<?php
$fiber = new Fiber(function (): void {
    $value = Fiber::suspend("hello");
    echo "Resumed with: $value\n";
});

$value = $fiber->start();    // "hello"
$fiber->resume("world");     // "Resumed with: world"

Misc 8.2 / 8.3 Wins

  • Typed class constants (8.3): const string VERSION = "1.0";
  • json_validate() (8.3): check JSON validity without decoding overhead
  • #[Override] attribute (8.3): static check that a method really overrides a parent
  • Readonly classes (8.2): shorthand fully-immutable objects
  • Standalone true / false / null types (8.2)
  • Constants in traits (8.2): trait T { const X = 1; }
  • Random extension (8.2): Random\Randomizer with pluggable engines
  • str_contains / str_starts_with / str_ends_with (8.0): the obvious string helpers

Next Steps

Frequently Asked Questions

The 7.x to 8.0 jump had some breaking changes (mostly stricter type checks and warnings becoming errors). Use Rector to auto-migrate code. 8.0 to 8.3 are mostly additive - few breaks.

PHP 8 introduced the JIT compiler. For typical web apps the speedup is modest (5-15%) because they're I/O-bound. CPU-heavy CLI scripts can be 2-3x faster.

Enums - they're type-safe. Passing Status::Active beats "active" because the IDE and engine catch typos and an enum value is unambiguously distinct from a random string.

Mostly inside libraries that build async runtimes (ReactPHP, AMPHP, Swoole). Application code rarely uses fibers directly. They're the building block, not the API you typically write.

readonly on a property (8.1) means write-once-then-frozen. readonly class (8.2) makes ALL properties readonly by default - shorthand for fully-immutable value objects.