Intersection Types (PHP 8.1)
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan, psalm, and rector — all specialist static analysis tools. Misuse (e.g. combining non-interface types, using intersection where a named interface would be cleaner, or trying to mix with union types before PHP 8.2 DNF) won't be caught by a default linter but will surface under these specialist tools.
Closest to 'simple parameterised fix' (e3). The quick_fix describes replacing a custom bridging interface or imprecise type hint with an intersection type (Countable&Iterator), or the reverse — extracting a named interface when overuse occurs. This is a targeted change within one component or signature, not a single-line swap but not a cross-cutting refactor either.
Closest to 'localised tax' (b3). Intersection types are scoped to individual function/method signatures. The applies_to scope is broad (web, cli, queue-worker) but the choice itself is localised — a type hint change in one signature doesn't propagate structural weight across the codebase unless the intersection pattern recurs widely, in which case a named interface would be extracted anyway.
Closest to 'notable trap' (t5). The misconception field explicitly states developers confuse intersection types with union types — thinking they are two ways to express the same thing. Additionally, the common_mistakes list includes the non-obvious restriction that PHP 8.1 intersection types cannot be combined with union types in the same declaration (requiring PHP 8.2 DNF types), and the constraint that only interfaces/classes are allowed, not scalars. These are documented gotchas most devs learn the hard way.
Also Known As
TL;DR
Explanation
Intersection types (PHP 8.1) declare that a value must implement all of the listed types. For example, function process(Countable&Iterator $collection) requires the argument to implement both interfaces. This is especially useful in generic-style code that needs an object to fulfil multiple contracts without creating a new combined interface. Intersection types only work with class/interface types, not with scalar types, and cannot currently be combined with union types in the same declaration (PHP 8.1).
Common Misconception
Why It Matters
Common Mistakes
- Using intersection types where a single interface combining both contracts would be cleaner.
- Combining non-interface types in an intersection — PHP only allows interfaces and classes, not scalar types.
- Not realising intersection types cannot be used with union types in the same declaration in PHP 8.1 — use PHP 8.2 DNF types (A&B)|null for that.
- Overusing intersection types making signatures hard to read — extract a named interface when the combination recurs.
Code Examples
// PHP 8.1 — cannot combine intersection and union:
function process(Countable&Iterator|null $input): void {} // Error in PHP 8.1
// PHP 8.2 DNF types solve this:
function process((Countable&Iterator)|null $input): void {} // Valid in PHP 8.2+
// PHP 8.1 — value must satisfy ALL listed types simultaneously
function process(Iterator&Countable \$collection): void {
echo count(\$collection); // Countable
foreach (\$collection as \$item) { handle(\$item); } // Iterator
}
// PHP 8.2 DNF types — mix union + intersection:
function accept((Iterator&Countable)|array \$items): void {}
// Practical: enforce a collection interface
interface Collection extends Countable, Iterator {}
function paginate(Countable&Iterator \$items, int \$perPage = 20): array {
return ['total' => count(\$items), 'items' => iterator_to_array(\$items)];
}