Intersection & DNF Types in Practice
debt(d1/e1/b3/t5)
Closest to 'caught instantly' (d1). Misusing intersection types with scalars (int&string) causes a fatal parse error at compile time. PHPStan and Psalm (from detection_hints.tools) also catch type mismatches statically, but the PHP parser itself catches the most common mistake immediately.
Closest to 'one-line patch' (e1). Per quick_fix, the fix is straightforward syntax adjustment: use A&B for objects, add parentheses for DNF (A&B)|null. These are single-line corrections to type declarations, not architectural changes.
Closest to 'localised tax' (b3). Intersection types are opt-in type declarations that affect only the specific function/method signatures where used. They don't impose system-wide constraints. However, once adopted in a codebase's interfaces, they do become part of the API contract that dependents must satisfy, creating some localized commitment.
Closest to 'notable trap' (t5). The misconception explicitly states developers assume intersection works with scalars like int&string when it only works with object types. Additionally, confusing union (A|B = either) with intersection (A&B = both) is a documented common mistake. These are gotchas that PHP developers eventually learn but aren't obvious from other language experience.
TL;DR
Explanation
Intersection types (PHP 8.1): function process(Countable&Iterator $c) — $c must implement BOTH interfaces. Only works with class/interface types, not scalars. DNF (Disjunctive Normal Form) types (PHP 8.2): (Countable&Iterator)|null — unions of intersections. The DNF form requires intersections in parentheses inside unions. Use cases: utility functions that require an object to implement multiple interfaces, nullable intersection types. PHPStan and Psalm both support intersection types for static analysis.
Common Misconception
Why It Matters
Common Mistakes
- Using intersection with scalar types — fatal error.
- Not using DNF parentheses for (A&B)|null — required syntax in PHP 8.2.
- Confusing union (A|B = either) with intersection (A&B = both).
Code Examples
// Can't use intersection with scalars:
function foo(int&string $x) {} // Fatal Error
interface Serializable {}
interface Loggable {}
// Must implement BOTH:
function process(Serializable&Loggable $obj): void {}
// PHP 8.2 DNF — nullable intersection:
function maybe((Serializable&Loggable)|null $obj): void {}