f in x
Intersection and Union Types in PHP 8 — Type Safety Without Compromise for Code That Won't Break at Runtime
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

Intersection and Union Types in PHP 8 — Type Safety Without Compromise for Code That Won't Break at Runtime

[2026-07-11] Author: Ing. Calogero Bono
Zenithby Meteora Web The operating system for your business. Social, clients, bookings and invoices in one platform. Gyms, barbers, professionals. Discover Zenith Free demo · no card

If you've ever written @param int|string $id in PHPDoc, you've already met union types. But PHP 8 makes them native, removing all ambiguity. And with PHP 8.1 comes the opposite: intersection types. We at Meteora Web have worked on PHP codebases for over 8 years — we've seen entire projects built on docblocks and manual checks that could have been solved with two well-placed characters. This guide takes you into the heart of PHP 8's type system, no fluff, with code you can copy today.

What Are Union Types and Why Use Them?

A union type declares that a value can be one of several types. In PHP 7.4 you could already use ?int for int|null, but for other combinations you had to rely on comments. With PHP 8.0 you write directly:

public function find(int|string $id): ?User { ... }

The advantage is immediate: the engine verifies the type at runtime and a modern IDE (PhpStorm, VS Code with intelephense) knows exactly what to expect. No more vague mixed or cascading checks. If your function accepts a numeric ID or a textual slug, union types are the right path.

Sponsored Protocol

Return types with unions

function fetchCustomer(int|string $identifier): Customer|false { ... }

Here false is a valid scalar type (typical pattern of built-in functions). In a real project, we prefer returning null instead of false — but if you work with legacy code, Customer|false is already a step up from mixed.

How to Use Intersection Types in PHP 8.1?

Intersection types (introduced in PHP 8.1) represent the exact opposite: a value must satisfy all types simultaneously. They are written with & and work only with object types (classes, interfaces, traits).

interface Renderable {
    public function render(): string;
}
interface Loggable {
    public function log(string $message): void;
}

function process(Renderable&Loggable $entity): void {
    echo $entity->render();
    $entity->log('processed');
}

The function process only accepts objects that implement both interfaces. If you pass an object implementing only Renderable, PHP throws a TypeError at runtime. Intersection types are perfect for combining contracts without multiple inheritance — much cleaner than abstract classes full of instanceof.

Sponsored Protocol

What Are the Differences Between Union and Intersection Types?

The difference is logical:

  • Union (|): “A or B” – at least one type must be satisfied.
  • Intersection (&): “A and B” – all types must be satisfied simultaneously.

Unions apply to any type (scalars, objects, null). Intersections work only on object types because a value cannot be simultaneously an integer and a string. Moreover, intersecting two classes that do not share a hierarchy is logically impossible — PHP signals it at compile time.

How to Handle null with Union Types?

PHP 8 made ?Type an alias of Type|null. You can write either:

public function getTitle(): ?string { ... }
// Equivalent:
public function getTitle(): string|null { ... }

We recommend using ?Type when it's a single union with null, and Type1|Type2|null when the combination is more complex. Readability matters.

Sponsored Protocol

Practical Examples of Union and Intersection Types in a Real Project

Case 1: Generic Repository with Multiple Identifiers

interface EntityRepository
{
    public function find(int|string $id): ?Entity;
    public function findByCriteria(string $field, int|string|float $value): array;
}

In one of our client platforms (Laravel/Livewire) we handle users with numeric IDs or UUIDs. One interface covers both cases without duplicating methods.

Case 2: Validator with Interface Intersection

interface Validatable {
    public function validate(): bool;
}
interface Persistable {
    public function save(): void;
}

function saveIfValid(Validatable&Persistable $entity): void {
    if ($entity->validate()) {
        $entity->save();
    }
}

The method saveIfValid only accepts entities that can be validated and saved. If tomorrow an object implements only Validatable, the compiler blocks it immediately, not at runtime after hours of testing.

Sponsored Protocol

Case 3: API Response with Error

function apiResponse(): array{status: string, data?: array|string}

This isn't a true union type on a property, but in an associative array you can use types similarly. For real safety, better create a typed DTO.

Common Mistakes to Avoid with Compound Types in PHP 8

  • Intersection with a scalar type: string&int makes no sense — PHP throws a parse error at compile time. Remember: only object types.
  • Redundant unions: int|string|int is valid but useless. PHP doesn't duplicate types, but clean up your code.
  • Forgetting explicit null: If a function can return null, always declare it. ?string or string|null, never just string if there's a chance.
  • Confusing | with &: Reading quickly, one slash can be misleading. We always put spaces between types: int | string (PHP accepts spaces).

What to Do Next

  1. Review your function signatures that use mixed or @param in docblocks. Convert them to union types where the possible cases are known (max 2-3 types).
  2. Create a marker interface if you need intersection types: e.g., Exportable and Printable, then use Exportable&Printable where both are required.
  3. Update your phpstan/psalm to the highest level: native types reduce false positives from static analyzers and make code self-documenting.
  4. Read the official PHP documentation for details on covariance/contravariance rules: PHP Type Declarations.
  5. If you use Laravel, take advantage of unions in custom form requests and repositories. We've written a more extensive guide in our PHP 8 Advanced Pillar — it also covers performance and async.

Types are not bureaucracy. They are contracts that the compiler enforces for free. Starting today with union and intersection types saves you hours of debugging tomorrow.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ 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()