PHP 8.1 — Intersection types
Concept
Intersection types (PHP 8.1) allow a parameter or return type to require that the value implements ALL specified interfaces simultaneously. The syntax is TypeA&TypeB. The value must satisfy every type in the intersection.
Difference from union types: Union types (A|B) mean "either A or B." Intersection types (A&B) mean "both A and B." A value satisfying an intersection type is more specific than one satisfying either constituent alone.
Use case — multiple interface requirements: When a function genuinely needs an object to be both Countable AND Iterable, Countable&Iterable expresses this precisely. Without intersection types, you'd need to create a new interface extending both, or use mixed and add a PHPDoc comment.
DNF types (PHP 8.2): Disjunctive Normal Form types combine unions and intersections: (Countable&Traversable)|null. An intersection must be wrapped in parentheses inside a union.
Limitations:
- Cannot contain a class in the intersection (only interfaces).
User&Loggableis invalid becauseUseris a class. - Cannot combine with
nulldirectly (A&B|nullrequires DNF:(A&B)|null). - Cannot use scalar types (
int&stringmakes no sense — a value can't be both).
Code Example
<?php
declare(strict_types=1);
interface Countable2
{
public function count(): int;
}
interface Iterable2
{
public function toArray(): array;
}
interface Stringable2
{
public function __toString(): string;
}
// Intersection type — must implement BOTH interfaces
function processCollection(Countable2&Iterable2 $collection): void
{
echo "Count: " . $collection->count() . "\n";
foreach ($collection->toArray() as $item) {
echo " - $item\n";
}
}
class StringCollection implements Countable2, Iterable2
{
private array $items;
public function __construct(string ...$items) { $this->items = $items; }
public function count(): int { return count($this->items); }
public function toArray(): array { return $this->items; }
}
$coll = new StringCollection('PHP', 'Laravel', 'MySQL');
processCollection($coll); // works — StringCollection implements both
// Only Countable2 — missing Iterable2
class CountOnlyCollection implements Countable2
{
public function count(): int { return 0; }
}
// processCollection(new CountOnlyCollection()); // TypeError
// Triple intersection
function displayItem(Countable2&Iterable2&Stringable2 $item): void
{
echo (string) $item . "\n";
echo "Has " . $item->count() . " sub-items\n";
}
// DNF types (PHP 8.2) — intersection within a union
function handleMaybeCollection((Countable2&Iterable2)|null $collection): int
{
return $collection?->count() ?? 0;
}
// Real-world: repository + cache combined
interface Repository { public function find(int $id): ?object; }
interface Cacheable { public function getCacheKey(): string; }
function findWithCache(Repository&Cacheable $source, int $id): ?object
{
$key = $source->getCacheKey() . ':' . $id;
// Try cache first, then source
return cache()->remember($key, 3600, fn() => $source->find($id));
}