f in x
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

Advanced PHP 8 — Typing, Performance, and Async for Modern Code

[2026-06-21] Author: Ing. Calogero Bono

Is your PHP code still full of nested foreach, endless switch statements, and classes that look like spreadsheets? You are not alone. PHP 8 changed the game, but many developers still use it as if it were PHP 5. The result: expensive maintenance, subpar performance, and bugs that only surface in production.

Here at Meteora Web, we have been working with PHP 8.x since its release. We have migrated legacy e-commerce platforms and built custom management systems from scratch. We know what it means to use PHP in production with tens of thousands of requests per day. And we have seen first-hand what happens when you ignore the new features: code that could be written in 10 lines requires 50, along with all the bugs that follow.

This is not a list of features in alphabetical order. It is a hands-on path to take your PHP to a professional level. We start from the foundations of typing, move through JIT performance, and reach concurrency with Fibers. Every section ends with something you can apply right now.

What are the main new features of PHP 8.4?

PHP 8.4, released at the end of 2024, introduces three major features that change how you write classes: Property Hooks, Asymmetric Visibility, and Lazy Objects. If you are coming from PHP 8.0, the leap is huge.

Property Hooks

Finally you can define getters and setters without writing separate methods. Instead of public function getName(): string { return $this->name; } you write directly on the property:

class User {
    public string $name {
        get => $this->name;
        set => $this->name = strtolower($value);
    }
}

This reduces boilerplate and makes code more readable. Note: it is not just syntactic sugar, because Property Hooks interact with serialization and inheritance in a predictable way.

Asymmetric Visibility

Now you can declare a property with different visibility for reading and writing. For example, a public protected(set) property allows anyone to read it, but only the class and its subclasses to modify it. Useful for immutable DTOs from the outside.

class Invoice {
    public protected(set) string $status = 'pending';
}

Lazy Objects

A native mechanism to create objects that are initialized only when needed. No longer needing third-party proxies. Use LazyObject::create() or LazyProxy::create().

Sponsored Protocol

$lazyUser = LazyObject::create(User::class, function() {
    return new User(loadFromDatabase(42));
});
// The database is queried only when you access $lazyUser->name

What to do now: Update your environment to PHP 8.4. Then look at classes with trivial getters/setters and convert them to Property Hooks.

How do named arguments and match expression make code more readable?

Named Arguments

Forget parameter order. In PHP 8 you can pass values by specifying the name:

function createReport(string $title, bool $exportPdf = false, bool $sendEmail = true): void { /* ... */ }

createReport(
    title: 'Annual Report 2026',
    sendEmail: false
);

No more null to skip optional parameters. Code becomes self-documenting.

Match Expression

match is safer than switch because it returns a value and throws an error if no case matches (unless a default is explicit). A real example from one of our e-commerce projects:

$status = match ($order->status) {
    'pending' => 'pending',
    'processing' => 'processing',
    'shipped', 'delivered' => 'completed',
    'cancelled' => 'cancelled',
    default => throw new InvalidArgumentException('Unknown status: ' . $order->status),
};

What to do now: Search your entire codebase for switch statements that return a value and convert them to match. Look for places where you use associative arrays as parameters and replace them with named arguments.

How useful are readonly properties and readonly classes?

Immutability is a cornerstone of reliable code. With PHP 8.1 you can declare readonly properties – set only once, typically in the constructor. PHP 8.2 adds readonly classes, making all properties readonly and prohibiting dynamic inheritance.

readonly class Configuration {
    public function __construct(
        public string $dbHost,
        public int $dbPort = 3306,
    ) {}
}

This eliminates entire categories of bugs: no more accidentally modified state, no more setter methods corrupting shared objects. In our development stack, we use readonly for DTOs, value objects, and configurations.

Sponsored Protocol

What to do now: Identify classes that represent immutable data (configurations, API responses, DTOs) and mark them as readonly. If you use promoted constructor, add readonly to properties that should not change.

How do Enums improve state management in PHP 8.1?

Before Enums, states were represented with class constants or magic strings. PHP 8.1 introduces native Enums, which can be pure or backed with values.

enum UserRole: string {
    case Admin = 'admin';
    case Editor = 'editor';
    case Viewer = 'viewer';
    
    public function label(): string {
        return match($this) {
            self::Admin => 'Administrator',
            self::Editor => 'Editor',
            self::Viewer => 'Viewer',
        };
    }
}

Enums are full objects: they can have methods, implement interfaces, and are passed by type. No more if ($role === 'admin') with typo risks. Backed enums integrate perfectly with databases: you can store $role->value and reconstruct the enum with UserRole::from($dbValue).

What to do now: Replace all class constants used as enumerations with real Enums. For database columns, use backed enums and map them in your models.

What are Fibers and when should you use them for concurrency?

Fibers (PHP 8.1) are concurrency primitives that let you write asynchronous code without external libraries. A Fiber is a block of code that can be paused and resumed by the caller, allowing multiple tasks to run concurrently in the same thread.

$fiber = new Fiber(function(): void {
    $result1 = Fiber::suspend('step1');
    echo $result1;
    $result2 = Fiber::suspend('step2');
    echo $result2;
});

$value = $fiber->start(); // 'step1'
$fiber->resume('Hello '); // prints Hello
$value = $fiber->resume('World'); // prints World

They are not suitable for true parallelism (for that you need processes or threads), but they are perfect for managing async I/O without callback hell. We used them in a notification system that needed to send emails and webhooks simultaneously, reducing wait times by 60%.

Note: Fibers do not replace a full event loop. For complex scenarios like async HTTP servers, consider libraries like ReactPHP or Amp, which use Fibers internally.

Sponsored Protocol

What to do now: Analyze parts of your code that perform blocking I/O sequentially (API calls, multiple queries, file writes). Wrap each operation in a Fiber and coordinate resumption with a simple scheduler.

Why do union types and intersection types make code safer?

PHP 8 introduces Union Types (TypeA|TypeB) and PHP 8.1 adds Intersection Types (TypeA&TypeB). Together they let you express exactly which types are accepted or returned.

Union Types

function findUserById(int|string $id): ?User { /* ... */ }

Instead of accepting mixed and doing is_int/is_string inside, you declare the union and PHP checks at call time. No surprises.

Intersection Types

function log(LoggerInterface&Countable $logger): void { /* ... */ }

The passed object must implement both interfaces. Useful when you want to enforce multiple contracts at once.

In our daily work, we use Union Types for flexible input parameters and Intersection Types for dependencies that must satisfy multiple interfaces. The result: fewer manual checks, more clarity, and better type errors at development time.

What to do now: Review all function signatures that use mixed or object and replace them with union types where possible. For dependencies requiring multiple interfaces, consider intersection types.

How does the JIT compiler improve performance?

The JIT (Just-In-Time) compiler introduced in PHP 8.0 compiles bytecode into machine code at runtime, accelerating loops and mathematical operations. However, it is not a magic wand: the gain is most visible in CPU-bound workloads (image processing, cryptography, simulations). For typical web applications (I/O and database), the improvement is more modest, often around 5-10%.

To enable it:

opcache.jit = tracing
opcache.jit_buffer_size = 100M

We measured it in a report generation system that performed calculations on 50,000 rows: execution time dropped from 12 seconds to 7.5 seconds – a nice saving for batch operations.

What to do now: Enable JIT in your production php.ini with opcache.jit=tracing. Run benchmarks with and without JIT on your heaviest functions. If you see improvements above 5%, keep it on.

Sponsored Protocol

Attributes: how to create declarative metadata in PHP 8?

Attributes (PHP 8.0) allow you to add structured metadata to classes, methods, properties, and parameters, without resorting to docblocks and manual reflection.

#[Route('/api/users', methods: ['GET'])]
class ListUsersController { /* ... */ }

You can also define custom attributes:

#[\Attribute(\Attribute::TARGET_CLASS)]
class Table {
    public function __construct(public string $name) {}
}

#[Table('users')]
class UserEntity { /* ... */ }

Then read them via Reflection: $reflectionClass->getAttributes(Table::class)[0]->newInstance()->name. This pattern is the foundation of modern frameworks like Symfony and Laravel for routing, validation, and serialization.

What to do now: Replace docblocks like @Route or @Table with native attributes. If you have code that reads docblocks with reflection, migrate to attribute reading – it is faster and type-safe.

How to manage dependencies with Composer and modern autoloading?

Composer is the de facto standard for PHP dependency management. Besides downloading packages, it handles autoloading per PSR-4. With PHP 8, autoloading is more efficient thanks to opcache.preload and pre-loaded classes.

We configure autoloading in composer.json:

{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

In production, we run composer install --no-dev --classmap-authoritative to generate an optimized autoloader, and we use opcache.preload to keep the most used classes in memory.

opcache.preload = /var/www/html/preload.php

Where preload.php contains:

$files = require __DIR__ . '/vendor/composer/autoload_classmap.php';
foreach ($files as $class => $path) {
    if (str_starts_with($class, 'App\\')) {
        opcache_compile_file($path);
    }
}

This reduces cold start times and improves stability during traffic spikes.

What to do now: Make sure your composer.json follows PSR-4. In production, use --classmap-authoritative. If you manage an application with many classes, implement opcache.preload loading only your own classes.

PHPUnit or Pest: which is the best testing framework for PHP 8?

Testing is not optional. PHP 8 does not introduce native testing tools, but the two main frameworks, PHPUnit and Pest, leverage the new features to be more expressive.

Sponsored Protocol

PHPUnit is the veteran. With PHP 8 you can use attributes for test configuration instead of docblocks:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;

class OrderTest extends TestCase {
    #[Test]
    #[DataProvider('statusProvider')]
    public function it_calculates_total(string $status, float $expected): void { /* ... */ }
}

Pest is more modern and lightweight, built on global functions:

test('order total is correct', function () {
    $order = new Order();
    $order->addItem(new Product('T-shirt', 29.99), 2);
    expect($order->total())->toBe(59.98);
});

We use both: PHPUnit for legacy projects that require compatibility, Pest for new developments. Pest takes advantage of closures and PHP 8 features, making tests shorter and more readable. It also natively supports attributes and named arguments.

What to do now: If you start from scratch or have a small project, begin with Pest. If you have an existing PHPUnit suite, you can gradually migrate using the pestphp/pest-plugin-laravel package or simply run both frameworks side by side.

What to do next: 5 actions to bring your project to advanced PHP 8

  1. Update your environment to PHP 8.4. Check for breaking changes with tools like Rector or PHPStan.
  2. Rewrite switch to match and replace positional parameters with named arguments where they improve readability.
  3. Introduce Enums for states and roles. Refactor class constants.
  4. Apply readonly properties to DTOs, configurations, and value objects. Make immutable data that should not change.
  5. Enable JIT and measure it. If you have no CPU-bound workloads, at least keep OpCache active and autoloading optimized.

PHP 8 is not just a new version: it is a paradigm shift. Stronger typing, lightweight concurrency, declarative metadata. When used to its full potential, your code becomes safer, faster, and easier to maintain. And the best part? You can start today, one step at a time.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()