PHP 8 didn't just arrive โ€” it arrived with a manifesto. Named arguments, fibers, enums, the JIT compiler, match expressions, readonly properties, union types, the nullsafe operator, and a raft of deprecations that finally cleared out two decades of cruft. If you're still writing PHP 7-style code on a PHP 8.x runtime, you're leaving significant clarity, safety, and performance on the table.

This guide is a practical tour of the features that actually matter in day-to-day work, with real-world examples drawn from production Drupal, Laravel, and API backend code.

1. Named Arguments & Fibers

Named Arguments (PHP 8.0) let you pass arguments to any function by parameter name, in any order, skipping optional parameters you don't need:

// Before: you had to remember positional order
array_slice($array, 0, null, true);

// After: intent is crystal-clear
array_slice(array: $array, offset: 0, preserve_keys: true);

This is a productivity win in heavily parameterised APIs โ€” particularly useful with Drupal's EntityQuery, FormState, and RenderArray patterns.

Fibers (PHP 8.1) bring cooperative concurrency to PHP without requiring ReactPHP or Amp. A Fiber is a lightweight, interruptible execution unit โ€” think of it as a green thread you control manually:

$fiber = new Fiber(function(): string {
    $value = Fiber::suspend('first');
    return 'done, received: ' . $value;
});

$first = $fiber->start();         // 'first'
$result = $fiber->resume('hello'); // 'done, received: hello'

Fibers underpin the async I/O primitives in modern PHP runtimes like Swoole and AMPHP 3, and are the foundation of non-blocking Drupal request handling in high-concurrency deployments.

2. Enums & Readonly Properties

Enums (PHP 8.1) replace the sprawling class-constant anti-pattern that every PHP codebase accumulated over years:

enum Status: string {
    case Draft     = 'draft';
    case Published = 'published';
    case Archived  = 'archived';
}

// Type-safe, autocompleted, comparable
function publish(Status $status): void {
    if ($status === Status::Draft) { /* ... */ }
}

Backed enums (with a string or int value) are particularly useful for mapping to database columns and Drupal field values without a translation layer.

Readonly Properties (PHP 8.1, extended in 8.2 to readonly classes) enforce immutability at the language level:

class BlogPost {
    public function __construct(
        public readonly int    $id,
        public readonly string $title,
        public readonly \DateTimeImmutable $publishedAt,
    ) {}
}

Once set in the constructor, these properties cannot be modified. This makes value objects and DTOs trivially safe โ€” no need for private properties + getters.

3. The Match Expression

The match expression is switch, redesigned for the type-safe era. It uses strict comparison, returns a value, throws UnhandledMatchError on no match, and supports multiple conditions per arm:

$label = match(true) {
    $score >= 90            => 'Excellent',
    $score >= 75            => 'Good',
    $score >= 60            => 'Pass',
    default                 => 'Fail',
};

// Multi-condition arm
$type = match($ext) {
    'jpg', 'jpeg', 'webp'   => 'image',
    'mp4', 'mov'            => 'video',
    default                 => 'other',
};

4. Union Types & the Nullsafe Operator

Union types let you declare that a parameter or return value can be one of several types. Combined with the nullsafe operator (?->), null-path code becomes dramatically cleaner:

// Union type: accepts int or float
function divide(int|float $a, int|float $b): int|float {
    return $a / $b;
}

// Nullsafe operator chains โ€” no more nested null checks
$city = $order?->getUser()?->getAddress()?->getCity();

5. JIT Compiler & Performance

PHP 8.0 shipped a JIT (Just-In-Time) compiler built on top of OPcache. For most web applications the gains are modest โ€” 5โ€“15% on CPU-bound workloads โ€” because the bottleneck is typically I/O (database, cache, network). But for computational PHP (image processing, CSV transforms, mathematical operations) the JIT can deliver 2โ€“3ร— speedups.

Enable it in php.ini:

opcache.enable=1
opcache.jit=tracing
opcache.jit_buffer_size=128M

In our Drupal benchmarks, enabling JIT with tracing mode reduced average page generation time by 11% on a content-heavy site with complex Views and computed fields.

6. Attributes (PHP 8.0)

Attributes replace docblock annotations with a first-class, compile-time-parseable syntax. Frameworks like Symfony 6+ and Drupal 11 use them extensively:

#[Route('/api/posts', methods: ['GET'])]
#[IsGranted('ROLE_EDITOR')]
public function listPosts(): JsonResponse { /* ... */ }

// Drupal 11 hook declaration
#[Hook('entity_presave')]
public function onEntityPresave(EntityInterface $entity): void { /* ... */ }

7. When to Upgrade

PHP 8.3 is the current stable release and the minimum for Drupal 11. PHP 7.4 reached end-of-life in November 2022 โ€” if you're still on it, you're running unpatched security vulnerabilities in production. Our upgrade path recommendation:

  • PHP 7.4 โ†’ 8.1 in one jump (if on Drupal 9.x โ€” move to 10/11 first)
  • PHP 8.1 โ†’ 8.3 is straightforward โ€” run phpstan and php-cs-fixer first
  • Drupal-specific: use drupal-check to flag deprecated API usage before upgrading PHP

PHP 8 is not just a version bump โ€” it's a language that rewards modern patterns. Enums, readonly classes, match expressions, and fibers will make your Drupal and API backends cleaner, faster, and considerably easier to maintain.